﻿using Artech.Common;
using Artech.Common.Properties;
using Microsoft.Practices.EnterpriseLibrary.Data;
using Microsoft.Practices.Unity.Utility;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Transactions;

namespace Artech.Core
{
    public class DefaultDataAccess: IDataAccess
    {
        public Database Database { get; private set; }
        public string ApplicationName { get; private set; }

        public DefaultDataAccess(string databseName, string applicationName, IServiceLocator serviceLocator = null)
        {
            Guard.ArgumentNotNullOrEmpty(databseName, "databseName");
            Guard.ArgumentNotNullOrEmpty(applicationName, "applicationName");
            serviceLocator = serviceLocator ?? ServiceLocatorBuilder.Current.GetServiceLocator();

            this.Database = serviceLocator.GetService<Database>(databseName);
            this.ApplicationName = applicationName;
        }


        private void Invoke(Action action)
        {
            Guard.ArgumentNotNull(action, "action");

            try
            {
                action();
            }
            catch (DbException ex)
            {
                if (ex.Message.Contains("ApplicationNameNotExists"))
                { 
                    throw new ApplicationNotFoundException(GeneralResources.ExceptionApplicationNotExists);
                }
                if (ex.Message.Contains("ConcurrentUpdated"))
                {
                    throw new DBConcurrencyException(GeneralResources.ExceptionConcurrentUpdated);
                }
                throw;
            }
        }
        private TResult Invoke<TResult>(Func<TResult> func)
        {
            Guard.ArgumentNotNull(func, "func");

            try
            {
                return func();
            }
            catch (DbException ex)
            {
                if (ex.Message.Contains("ApplicationNameNotExists"))
                {
                    throw new ApplicationNotFoundException(GeneralResources.ExceptionApplicationNotExists);
                }
                if (ex.Message.Contains("ConcurrentUpdated"))
                {
                    throw new DBConcurrencyException(GeneralResources.ExceptionConcurrentUpdated);
                }
                throw;
            }
        }

        public IDataReader ExecuteReader(string procedureName, params object[] parameterValues)
        {
           return Invoke<IDataReader>(()=> this.Database.ExecuteReader(procedureName, parameterValues));
        }

        public int ExecuteNonQuery(string procedureName, params object[] parameterValues)
        {
            return Invoke<int>(() => this.Database.ExecuteNonQuery(procedureName, parameterValues));
        }

        public int ExecuteNonQuery(DbCommand command)
        {
            return Invoke<int>(() => this.Database.ExecuteNonQuery(command));
        }

        public void ExecuteTransactionalOperations(Action<DbTransaction> actionWithTransaction, Action actionWithoutTransaction)
        {
            Guard.ArgumentNotNull(actionWithTransaction, "actionWithTransaction");
            Guard.ArgumentNotNull(actionWithoutTransaction, "actionWithoutTransaction");

            if (string.IsNullOrEmpty(ApplicationContext.Current.TransactionId))
            {
                ApplicationContext.Current.TransactionId = Guid.NewGuid().ToString();
            }

            if (Transaction.Current == null)
            {
                DbConnection connection = this.Database.CreateConnection();
                connection.Open();
                DbTransaction transaction = connection.BeginTransaction();
                try
                {
                    Invoke(()=> actionWithTransaction(transaction));
                    transaction.Commit();
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
                finally
                {
                    transaction.Dispose();
                    connection.Close();
                }
            }
            else
            {
                Invoke(() => actionWithoutTransaction());
            }
        }
    }
}